package cn.uncode.dal.utils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.springframework.cglib.beans.BeanMap;

import cn.uncode.dal.core.BaseDTO;
import cn.uncode.dal.descriptor.ForeignKey;
import cn.uncode.dal.descriptor.resolver.JavaType;

public class BeanUtil{
	
	private static final String FOREIGN_KEY_SUFFIX = "Id";
	
	private static final LinkedHashMap<Class<?>, Set<ForeignKey>> FOREIGN_KEY_CACHE = new LinkedHashMap<>();
	
	public static Map<String, Object> objToMap(Object obj) {
		Map<Object, Object> inMap = BeanMap.create(obj);
		Map<String, Object> result = new HashMap<String, Object>();
		matchFieldName2TableFieldByAnnotation(inMap, result, obj.getClass());
		return result;
    }
	
	public static <T> T mapToObj(Map<String, Object> map, Class<T> valueType) {
		if (null != map) {
			Map<Object, Object> mapObj = new HashMap<>();
			mapObj.putAll(map);
	        return convertMapToObject(mapObj, valueType);  
		}
		return null;
	}
	
	private static <T> T convertMapToObject(Map<Object, Object> inputMap, Class<T> clazz) {
		IgnoreCaseHashMap<Object, Object> input = new IgnoreCaseHashMap<Object, Object>();
		input.putAll(inputMap);
		T t = null;
		try {
			t = clazz.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		BeanMap beanMap = BeanMap.create(t);
		for(Field fd:clazz.getDeclaredFields()){
    		if(!Modifier.isStatic(fd.getModifiers())){
    			String fdName = fd.getName();
    			cn.uncode.dal.annotation.Field field = fd.getAnnotation(cn.uncode.dal.annotation.Field.class);
    			if(field != null) {
    				fdName = field.name();
    			}
    			Object value = input.get(fdName);
    			if(null != value) {
    				if(JavaType.isBaseType(fd.getType())) {
    					beanMap.put(fd.getName(), value);
        			}else {
        				Class<?> fdClass = fd.getType();
        				Map<Object, Object> fdInput = (Map<Object, Object>)value;
        				Object obj = convertMapToObject(fdInput, fdClass);
        				if(null != obj) {
        					beanMap.put(fd.getName(), obj);
        				}
        			}
    			}
    		}
    	}
    	Class<?> supperClass = clazz.getSuperclass();
    	while(null != supperClass && !Object.class.getName().equals(supperClass.getName())){
    		for(Field fd:supperClass.getDeclaredFields()){
        		if(!Modifier.isStatic(fd.getModifiers())){
        			String fdName = fd.getName();
        			cn.uncode.dal.annotation.Field field = fd.getAnnotation(cn.uncode.dal.annotation.Field.class);
        			if(field != null) {
        				fdName = field.name();
        			}
        			Object value = input.get(fdName);
        			if(null != value) {
        				if(JavaType.isBaseType(fd.getType())) {
        					beanMap.put(fd.getName(), value);
            			}else {
            				Class<?> fdClass = fd.getType();
            				Map<Object, Object> fdInput = (Map<Object, Object>)value;
            				Object obj = convertMapToObject(fdInput, fdClass);
            				if(null != obj) {
            					beanMap.put(fd.getName(), obj);
            				}
            			}
        			}
        		}
        	}
    		supperClass = supperClass.getSuperclass();
    	}
    	return clazz.cast(beanMap.getBean());
    }
	
	private static void matchFieldName2TableFieldByAnnotation(Map<Object, Object> input, Map<String, Object> output, Class<?> clazz) {
		matchFieldName2TableFieldByAnnotation(input, output, clazz, true);
	}
	
	private static void matchFieldName2TableFieldByAnnotation(Map<Object, Object> input, Map<String, Object> output, Class<?> clazz, boolean lowerCase) {
		for(Field fd:clazz.getDeclaredFields()){
    		if(!Modifier.isStatic(fd.getModifiers())){
    			String fdName = fd.getName();
    			if(input.containsKey(fdName)) {
    				Object value = input.get(fdName);
    				cn.uncode.dal.annotation.Field fdAnnotation = fd.getAnnotation(cn.uncode.dal.annotation.Field.class);
    				if(JavaType.isBaseType(fd.getType())) {
                		if(null != fdAnnotation){
                			fdName = fdAnnotation.name();
                		}
                		if(null != value && "null".equals(value) == false) {
                			if(lowerCase) {
                				output.put(fdName.toLowerCase(), value);
                			}else {
                				output.put(fdName, value);
                			}
                		}
        			}else {
        				Class<?> fdClass = fd.getType();
        				if(fdClass == java.util.List.class){
                            // 如果是List类型，得到其Generic的类型
                            Type genericType = fd.getGenericType();
                            // 如果是泛型参数的类型
                            if(genericType instanceof ParameterizedType){
                                ParameterizedType pt = (ParameterizedType) genericType;
                                //得到泛型里的class类型对象
                                Class<?> clazzw = (Class<?>)pt.getActualTypeArguments()[0];
                                if(null != input.get(fd.getName())) {
                					List<Object> fdInput = (List<Object>)input.get(fd.getName());
                					List<Map<String, Object>> listOutput = new ArrayList<>();
                					for(Object item:fdInput) {
                						Map<Object, Object> inMap = BeanMap.create(item);
                						Map<String, Object> fdOutput = new HashMap<>();
                    					matchFieldName2TableFieldByAnnotation(inMap, fdOutput, clazzw, lowerCase);
                    					listOutput.add(fdOutput);
                					}
                					if(lowerCase) {
                        				output.put(fdName.toLowerCase(), listOutput);
                        			}else {
                        				output.put(fdName, listOutput);
                        			}
                				}
                            }
                        }else {
                        	if(null != input.get(fd.getName())) {
            					Map<Object, Object> fdInput = (Map<Object, Object>)input.get(fd.getName());
            					Map<String, Object> fdOutput = new HashMap<>();
            					matchFieldName2TableFieldByAnnotation(fdInput, fdOutput, fdClass, lowerCase);
            					if(lowerCase) {
                    				output.put(fdName.toLowerCase(), fdOutput);
                    			}else {
                    				output.put(fdName, fdOutput);
                    			}
            				}
                        }
        			}
    			}
    		}
    	}
    	Class<?> supperClass = clazz.getSuperclass();
    	while(null != supperClass && !Object.class.getName().equals(supperClass.getName())){
    		matchFieldName2TableFieldByAnnotation(input, output, supperClass, lowerCase);
    		supperClass = supperClass.getSuperclass();
    	}
    }
	
/*	private static Map<String, Object> toLowerCaseMap(Map<?, ?> input){
		Map<String, Object> rt = new HashMap<>();
		if(null != input) {
			for(Entry<?, ?> item : input.entrySet()) {
				rt.put(String.valueOf(item.getKey()).toLowerCase(), item.getValue());
			}
		}
		return rt;
	}*/
	
	public static Set<ForeignKey> loadForeignKeyFromClass(Class<?> clazz){
		Set<ForeignKey> fkeys = new HashSet<>();
		if(FOREIGN_KEY_CACHE.containsKey(clazz)) {
			fkeys.addAll(FOREIGN_KEY_CACHE.get(clazz));
			return fkeys;
		}
		loadForeignKeyByAnnotation(clazz, fkeys);
		FOREIGN_KEY_CACHE.put(clazz, fkeys);
		return fkeys;
	}
	
	private static void loadForeignKeyByAnnotation(Class<?> clazz, Set<ForeignKey> foreignKeys) {
    	for(Field fd:clazz.getDeclaredFields()){
    		if(!Modifier.isStatic(fd.getModifiers())){
    			String fdName = fd.getName();
    			String tbName = null;
				if(!JavaType.isBaseType(fd.getType())) {
					ForeignKey foreignKey = new ForeignKey();
					cn.uncode.dal.annotation.Field fdAnnotation = fd.getAnnotation(cn.uncode.dal.annotation.Field.class);
            		if(null != fdAnnotation){
            			fdName = fdAnnotation.name();
            			foreignKey.setFdTableName(fdAnnotation.tableColumn());
            		}
            		foreignKey.setFdName(fdName);
            		foreignKey.setFdClass(fd.getType());
            		cn.uncode.dal.annotation.Table tbAnnotation = fd.getType().getAnnotation(cn.uncode.dal.annotation.Table.class);
            		if(null != tbAnnotation) {
            			tbName = tbAnnotation.name();
            		}else {
            			String className = fd.getType().getName();
            			className = className.substring(className.lastIndexOf(".") + 1);
            			if(className.indexOf(BaseDTO.DTO_SUFFIX) != -1) {
            				tbName = className.replace(BaseDTO.DTO_SUFFIX, "").trim();
            			}else {
            				tbName = className;
            			}
            		}
            		foreignKey.setTbName(tbName.toLowerCase());
            		if(StringUtils.isBlank(foreignKey.getFdTableName())) {
            			foreignKey.setFdTableName(fdName + FOREIGN_KEY_SUFFIX);
            		}
            		foreignKeys.add(foreignKey);
            		loadForeignKeyByAnnotation(fd.getType(), foreignKey.getForeignKeys());
    			}
    		}
    	}
    }
	
	public static ForeignKey getBeafForeignKey(ForeignKey foreignKey, Map<String, Object> content) {
		if(foreignKey != null) {
			Map<String, Object> params = new HashMap<>();
			if(null != content && null != content.get(foreignKey.getFdName())){
				params.putAll((Map<String, Object>)content.get(foreignKey.getFdName()));
			}
			if(foreignKey.hasForeignKey()) {
				for(ForeignKey fk : foreignKey.getForeignKeys()) {
					if(idIsNotNull(fk.getId())) {
						continue;
					}
					return getBeafForeignKey(fk, params);
				}
				if(idIsNull(foreignKey.getId())) {
					foreignKey.setParams(params);
					return foreignKey;
				}
			}else {
				foreignKey.setParams(params);
				return foreignKey;
			}
		}
		return null;
	}
	
	
	public static ForeignKey getBeafForeignKey(Set<ForeignKey> foreignKeys, Map<String, Object> content) {
		for(ForeignKey foreignKey : foreignKeys) {
    		ForeignKey beafFK = BeanUtil.getBeafForeignKey(foreignKey, content);
    		if(beafFK != null) {
    			return beafFK;
    		}else {
    			if(idIsNull(foreignKey.getId())) {
    				Map<String, Object> params = (Map<String, Object>)content.get(foreignKey.getFdName());
    				foreignKey.setParams(params);
    				return foreignKey;
    			}
    		}
    	}
		return null;
	}
	
	public static boolean idIsNull(Object id) {
		if(null == id) {
			return true;
		}
		if("0".equals(String.valueOf(id))) {
			return true;
		}else {
			return false;
		}
	}
	
	public static boolean idIsNotNull(Object id) {
		return idIsNull(id) == false;
	}
	

}
